/**
 *                     Remixvym (version 0.8.4)
 *             Copyright (c) 2010 by RemixLab, DISI-UNAL
 *            http://www.disi.unal.edu.co/grupos/remixlab/
 *
 * This Java package provides classes to visualize Mind Maps created in VYM
 * (View Your Mind) in a Processing Sketch
 *
 * @author Alvaro Lopez, Juan Baquero
 *
 * This source file is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option)
 * any later version.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * A copy of the GNU General Public License is available on the World Wide Web
 * at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by
 * writing to the Free Software Foundation, 51 Franklin Street, Fifth Floor
 * Boston, MA 02110, USA
 */
package remixlab.remixvym.core.util;

//import java.util.ArrayList;

import processing.core.PApplet;
import processing.core.PFont;
import remixlab.remixvym.MindMap;
import remixlab.remixvym.core.graphics.StyleGraph;
import remixlab.remixvym.core.mindmap.Node;

/**
 * This class sets all the XML coordinates like positives, and implements the Center and Scale 
 * modes for the map. 
 * <p> The map saves the delay and edge by default. All nodes are set with delay into X and Y
 * from the edge. The height and width sizes of the MindMap contains this increase of size "2 x Edge".   
 */
public class MindMapUtil {
	
	/**
	 * Inits the map setting the root using {@link #search(Node)}, the video init {@link #intNodeVideo(Node, StyleGraph)}
	 * and sets the variables for the map (margin, xMin, yMin, xMax, yMax.
	 * <p> Resets the map, and puts it into the original form {@link #resetMap()}.
	 */
	public void beginMap(MindMap map,StyleGraph style)
	{
		resetMap();
		search(map.getMap());
		margin = defaultMargin;
		xMin = xMin-margin;
		yMin = yMin-margin;
		xMax = xMax+margin;
		yMax = yMax+margin;
		
		map.setHeight((int)(yMax-yMin));
		map.setWidth((int)(xMax-xMin));
		map.setHeightOriginal((int)(yMax-yMin));
		map.setWidthOriginal((int)(xMax-xMin));
		map.setScale(1);
		updateNodes(map.getMap());
	}
	
	/**
	 *Resets the map starting again the original values of xMin, yMin, xMax, yMax, margin, delayY and delayX.
	 */		
	public void resetMap()
	{
		xMin = Integer.MAX_VALUE;
		xMax= Integer.MIN_VALUE;
		yMin= Integer.MAX_VALUE;
		yMax= Integer.MIN_VALUE;
		delayY= 0;
		delayX= 0;
		rate= 1;
		margin = defaultMargin;
	}
	
	/**
	 * Updates the map according the actual position.
	 * @param map: The actual map. 
	 */	
	public void updateMap(MindMap map)
	{
		updateSize(map);
		updateNodes(map.getMap());
	}
	
	/**
	 * Updates a section of the map according the position of a specific node.
	 * @param node: The starting update node. 
	 */	
	private void updateNodes(Node node)
	{
		node.x1 = node.x1 - xMin;
		node.x2 = node.x2 - xMin;
		node.y1 = node.y1 - yMin;
		node.y2 = node.y2 - yMin;
		node.calculate();
		
		for(int i=0;i<node.children.size();i++)
		{
			if(!node.children.get(i).hide)
				updateNodes(node.children.get(i));
		}
	}
	
	/**Update the map's width and height properties*/
	private void updateSize(MindMap map)
	{
		Node node= map.getMap();
		search(node);
		
		float doubleMargin = (2*margin);
		
		xMin = xMin-margin;
		yMin = yMin-margin;
		xMax = xMax+margin;
		yMax = yMax+margin;
		
		float widthLocal = xMax-xMin;
		float heightLocal = yMax-yMin;
		
		map.setWidthOriginal((int)(((double)(widthLocal-doubleMargin)/map.getScale())+(double)doubleMargin));
		map.setHeightOriginal((int)(((double)(heightLocal-doubleMargin)/map.getScale())+(double)doubleMargin));
		map.setHeight((int)heightLocal);
		map.setWidth((int)widthLocal);
	}
	
	/**Recursively updates the coordinates limits according to nodes' positions*/
	private void search(Node node)
	{
		if(node.x1<xMin)
			xMin=(int)node.x1;
		
		if(node.y1<yMin)
			yMin=(int)node.y1;
		
		if(node.x2>xMax)
			xMax=(int)node.x2;
		
		if(node.y2>yMax)
			yMax=(int)node.y2;
		
		for(int i=0;i<node.children.size();i++)
		{
			if(!node.children.get(i).hide)
				search(node.children.get(i));
		}
		
	}
	
	/**
	 * Change the size of the map, according the chosen mode {@link #CENTER} and {@link #SCALE}.
	 * The margin is set pursuant the mode.
	 * <p>
	 * <b>See: </b> {@link #center(MindMap, int, int)}, {@link #scale(MindMap, int, int)}.
	 * @param map The actual map.
	 * @param width: Width of the scene.
	 * @param height: Height of the scene.
	 * @param mode: Scale mode chosen.
	 */
	public void changeSize(MindMap map, int width, int height,int mode) {
		margin=defaultMargin;
		centerOrScale(map, width, height);
	}
	
	/**Centers the map according to its size*/
	private void center(MindMap map, int width, int height) {

		delayY = ((height - map.getHeight())/2);//-delayY;
		delayX = ((width - map.getWidth())/2);//-delayX;
		centerNode(map.getMap());
		map.setHeight((int)height);
		map.setWidth((int)width);
	}
	
	/**Centers a node respecting to its parent*/
	private void centerNode(Node node)
	{
		node.x1 = node.x1 + delayX + margin;
		node.x2 = node.x2 + delayX + margin;
		node.y1 = node.y1 + delayY + margin;
		node.y2 = node.y2 + delayY + margin;
		node.calculate();
		
		for(int i=0;i<node.children.size();i++)
		{
			if(!node.children.get(i).hide)
				centerNode(node.children.get(i));
		}
		
	}
	
	/**Change the visual dimensions of the map and update it*/
	private void scale(MindMap map, int width, int height) {
		double scale;
		int doubleMargin = (2*margin);
		double rateLocal = ((double)map.getWidthOriginal()-doubleMargin)/((double)map.getHeightOriginal()-doubleMargin); // PRIMERO HALLAMOS LA RELACION X/Y DEL TAMANYO ORIGINAL
		
		width = width-doubleMargin;
		height = height-doubleMargin;
		
		double tempLong = ((double)height*rateLocal); // HALLAMOS EL VALOR DEL ANCHO QUE DEBERIA SER CON ESE ALTO
		
		if(tempLong>width) 
		{
			tempLong = ((double)width/rateLocal); //SI ES MAYOR HALLAMOS EL VALOR DEL ALTO QUE DEDBERIA SER CON ESE ANCHO
			rateLocal = ((double)width/(double)(map.getWidth()-doubleMargin)); //HALLAMOS LA TAZA DE CAMBIO DEL TAMANYO DIBUJADO
			scale = (double)width/(double)(map.getWidthOriginal()-doubleMargin); //HALLAMOS LA ESCALA REAL CON RESPECTO AL TAMANYO ORIGINAL
			
		}else
		{
			rateLocal = (double)height/(double)(map.getHeight()-doubleMargin); //HALLAMOS LA TAZA DE CAMBIO DEL TAMANYO DIBUJADO
			scale = (double)height/(double)(map.getHeightOriginal()-doubleMargin); //HALLAMOS LA ESCALA REAL CON RESPECTO AL TAMANYO ORIGINAL
		}
		rate = rateLocal;
	    scaleNode(map.getMap());
	    
	    map.setHeight((int)(((double)(map.getHeight()-doubleMargin)*rate)+(double)doubleMargin));
	    map.setWidth((int)(((double)(map.getWidth()-doubleMargin)*rate)+(double)doubleMargin));
	    
	    map.setScale(scale);
	    center(map, width+doubleMargin, height+doubleMargin);
	}
	
	/**Change the visual dimensions of the node and its children and update them*/
	private void scaleNode(Node node)
	{
		
		float x1 = (float)((double)(node.x1-margin)*rate);
		float x2 = (float)((double)(node.x2-margin)*rate);
		float y1 = (float)((double)(node.y1-margin)*rate);
		float y2 = (float)((double)(node.y2-margin)*rate);
		
		node.x1 = (x1-(int)x1<=0.5)?(int)x1:(int)x1+1;
		node.x2 = (x2-(int)x2<=0.5)?(int)x2:(int)x2+1;
		node.y1 = (y1-(int)y1<=0.5)?(int)y1:(int)y1+1;
		node.y2 = (y2-(int)y2<=0.5)?(int)y2:(int)y2+1;
		
		node.calculate();
		
		for(int i=0;i<node.children.size();i++)
		{
			if(!node.children.get(i).hide)
				scaleNode(node.children.get(i));
		}
	}
	
	/***If the new size is smaller or equal to the original map size, resize it to the the original size
	 * And, independently of that conversion, centers the map*/
	private void centerOrScale(MindMap map, int width, int height)
	{
		if(map.getHeightOriginal()<=height && 
		   map.getWidthOriginal()<=width){
			
			if(map.getScale()!=1.0)
				scaleOne(map);
			
			margin=0;
			center(map,width,height);
			
		}else
		{
			scale(map,width,height);
		}
	}
	
	/**Scales the map to its original size*/
	private void scaleOne(MindMap map) {
		
		rate = map.getScale();
	    scaleOneNode(map.getMap());
	    
	    map.setHeight(map.getHeightOriginal());
	    map.setWidth(map.getWidthOriginal());
	    
	    map.setScale(1.0);
	    
	    center(map, map.getWidthOriginal(),map.getHeightOriginal());
	}
	
	/**Scales a node (and recursively its children) to its original size*/
	private void scaleOneNode(Node node)
	{
		float x1 = (float)((double)(node.x1-margin)/rate);
		float x2 = (float)((double)(node.x2-margin)/rate);
		float y1 = (float)((double)(node.y1-margin)/rate);
		float y2 = (float)((double)(node.y2-margin)/rate);
		
		node.x1 = (x1-(int)x1<=0.5)?(int)x1:(int)x1+1;
		node.x2 = (x2-(int)x2<=0.5)?(int)x2:(int)x2+1;
		node.y1 = (y1-(int)y1<=0.5)?(int)y1:(int)y1+1;
		node.y2 = (y2-(int)y2<=0.5)?(int)y2:(int)y2+1;
		
		node.calculate();
		
		for(int i=0;i<node.children.size();i++)
		{
			if(!node.children.get(i).hide)
				scaleOneNode(node.children.get(i));
		}
	}
	
	/**returns the rate*/
	public double getRateChange()
	{
		return rate;
	}
	
    /*
     * esta funcion retorna el tamaño de la fuente que debe tener el texto en el nodo
     */
	public float textNoRecursiva(int distancia,String str,PApplet parent){
		
		float total=0;
		float size=2;
		float epsilon = 0.02f;
		PFont font;
		float dist = distancia*0.8f;
		font = parent.createFont("verdana",size);
		parent.textFont(font);
		do{
			size=size + 4f;
			parent.textSize(size);
			total = parent.textWidth(str);
		}while(total<dist);
		if(total<=dist+epsilon) return size;
		size = size-4;
		do{
			size=size + 1f;
			parent.textSize(size);
			total = parent.textWidth(str);
		}while(total<dist);
		if(total<=dist+epsilon) return size;
		size = size-1;
		do{
			size=size + 0.2f;
			parent.textSize(size);
			total = parent.textWidth(str);
		}while(total<dist);
		if(dist <= total+epsilon) return size;
		do{
			size=size + 0.02f;
			parent.textSize(size);
			total = parent.textWidth(str);
		}while(total<dist);
		return size-0.02f;
	}
	//Max and min coordinate values
	private int xMin;
	private int xMax;
	private int yMin;
	private int yMax;
	//Space between nodes
	private int delayY;
	private int delayX;

	//Ratio for scaling
	private double rate; //TAZA DE CAMBIO PARA EL SCALE
	
	/**Space between the applet border and any map element into the scene*/
	private int margin;
	private final int defaultMargin = 15;
	
	/**Integer parameter that defines the map like Center mode.*/
	public final static int CENTER = 0;
	/**Integer parameter that defines the map like Scale mode.*/
	public final static int SCALE = 1;
	/**Integer parameter that defines the map like Real mode.*/
	public final static int REAL = 2;
}
